package com.onpositive.gamemaker.editor.io;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.ui.ide.FileStoreEditorInput;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.onpositive.gamemaker.editor.GameCreatorEditorPlugin;
import com.onpositive.gamemaker.spriteeditor.SpriteCategory;
import com.onpositive.gamemaker.spriteeditor.SpriteDescriptor;

public class XMLSpritePersistance
{
	private static final String SAVE_FAILED_MSG = "Save failed";
	private static final String YOFFSET_ATTR = "yoffset";
	private static final String XOFFSET_ATTR = "xoffset";
	private static final char BOUND_START_BRACE = '[';
	private static final char BOUND_END_BRACE = ']';
	private static final String FILE_NAME_ATTR = "fileName";
	private static final String TYPE_ATTR = "type";
	private static final String NAME_ATTR = "name";
	private static final String CATEGORY_ATTR = "category";
	
	private static final String SPRITES_TYPE = "sprites";
	private static final String SPRITE_TYPE = "sprite";
	private static final String CATEGORY_TYPE = "category";

	public static Map<String, SpriteCategory> loadDescriptorList(InputStream inputStream) throws ConfigPersistanceException
	{
		try
		{
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = builder.parse(inputStream);
			return processRootElement(doc.getDocumentElement());
		} catch (MalformedURLException e)
		{
			throw new ConfigPersistanceException("Error loading xml input file" + e);
		} catch (IOException e)
		{
			throw new ConfigPersistanceException("Error loading xml input file" + e);
		} catch (ParserConfigurationException e)
		{
			throw new ConfigPersistanceException("Error loading xml input file" + e);
		} catch (SAXException e)
		{
			throw new ConfigPersistanceException("Error loading xml input file" + e);
		}
	}

	private static Map<String, SpriteCategory> processRootElement(Element documentElement)
	{
		List<SpriteDescriptor> resultList = new ArrayList<SpriteDescriptor>();
		Map<String, SpriteCategory> categories = new HashMap<String, SpriteCategory>();
		NodeList childNodes = documentElement.getChildNodes();
		for (int i = 0; i < childNodes.getLength(); i++)
		{
			Node item = childNodes.item(i);
			if (item.getNodeType() == Node.ELEMENT_NODE)
			{
				Element element = (Element) item;
				if (element.getNodeName().equalsIgnoreCase(SPRITE_TYPE))
					resultList.add(parseSpriteDescriptor(element,categories));
				else if (element.getNodeName().equalsIgnoreCase(CATEGORY_TYPE))
				{
					SpriteCategory category = parseCategory(element);
					categories.put(category.getCategoryName(), category);
				}
					
			}
		}
		return categories;
	}

	private static SpriteCategory parseCategory(Element element)
	{
		String name = element.getAttribute(NAME_ATTR);
		return new SpriteCategory(name);
	}

	private static SpriteDescriptor parseSpriteDescriptor(Element element, Map<String, SpriteCategory> categories)
	{
		SpriteDescriptor descriptor;
		String type = element.getAttribute(TYPE_ATTR);
		String category = element.getAttribute(CATEGORY_ATTR);
		String fileName = element.getAttribute(FILE_NAME_ATTR);
		if (fileName.indexOf(BOUND_START_BRACE) > 0)
		{
			try
			{
				int x,y,width,height;
				int startIdx = fileName.indexOf(BOUND_START_BRACE);
				int endIdx = fileName.lastIndexOf(BOUND_END_BRACE);
				if (endIdx < startIdx)
					endIdx = fileName.length();
				String bounds = fileName.substring(startIdx + 1, endIdx);
				fileName = fileName.substring(0,startIdx).trim();
				StringTokenizer tokenizer = new StringTokenizer(bounds,",");
				x = Integer.parseInt(tokenizer.nextToken());
				y = Integer.parseInt(tokenizer.nextToken());
				width = Integer.parseInt(tokenizer.nextToken());
				height = Integer.parseInt(tokenizer.nextToken());
				descriptor = new SpriteDescriptor(type, fileName,x,y,width,height);
			}
			catch (Exception e) {
				descriptor = new SpriteDescriptor(type, fileName);
			}
		}
		else
			descriptor = new SpriteDescriptor(type, fileName);
		if (element.hasAttribute(XOFFSET_ATTR))
		{
			try
			{
				descriptor.setXoffset(Integer.parseInt(element.getAttribute(XOFFSET_ATTR)));
			}
			catch (NumberFormatException e) {
			}
		}
		if (element.hasAttribute(YOFFSET_ATTR))
		{
			try
			{
				descriptor.setYoffset(Integer.parseInt(element.getAttribute(YOFFSET_ATTR)));
			}
			catch (NumberFormatException e) {
			}
		}
		
		SpriteCategory spriteCategory = categories.get(category);
		if (spriteCategory != null)
			spriteCategory.addSprite(descriptor);
		else
		{
			SpriteCategory defaultCategory = categories.get(SpriteCategory.DEFAULT_CATEGORY_NAME);
			if (defaultCategory == null)
			{
				defaultCategory = new SpriteCategory(SpriteCategory.DEFAULT_CATEGORY_NAME);
				categories.put(SpriteCategory.DEFAULT_CATEGORY_NAME, defaultCategory);
			}
			defaultCategory.addSprite(descriptor);
		}
		
		return descriptor;
	}
	
	public static void saveDescriptorList(OutputStream outputStream, Map<String, SpriteCategory> categoriesMap) throws ConfigPersistanceException
	{
		try
		{
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			Document doc = builder.newDocument();
			Element rootElement = doc.createElement(SPRITES_TYPE);
			doc.appendChild(rootElement);
			String[] categories = categoriesMap.keySet().toArray(new String[0]);
			for (int i = 0; i < categories.length; i++)
			{
				Element category = doc.createElement(CATEGORY_TYPE);
				category.setAttribute(NAME_ATTR,categoriesMap.get(categories[i]).getCategoryName());
				rootElement.appendChild(category);
			}
			
			for (int i = 0; i < categories.length; i++)
			{
				SpriteDescriptor[] spriteList = categoriesMap.get(categories[i]).getSpriteList();
				for (int j = 0; j < spriteList.length; j++)
				{
					rootElement.appendChild(createElement(doc,spriteList[j], categories[i]));
				}
			}
			
			 TransformerFactory transformerFactory = TransformerFactory
	          .newInstance();
	      Transformer transformer = transformerFactory.newTransformer();
	      transformer.setOutputProperty("omit-xml-declaration", "yes");
	      transformer.setOutputProperty(OutputKeys.INDENT, "yes");
	      StreamResult result = new StreamResult(outputStream);
	      transformer.transform(new DOMSource(rootElement), result);
		} catch (TransformerConfigurationException e)
		{
			throw new ConfigPersistanceException(SAVE_FAILED_MSG,e);
		} catch (TransformerException e)
		{
			throw new ConfigPersistanceException(SAVE_FAILED_MSG,e);
		} catch (ParserConfigurationException e)
		{
			throw new ConfigPersistanceException(SAVE_FAILED_MSG,e);
		}
	}

	private static Node createElement(Document doc, SpriteDescriptor spriteDescriptor, String category)
	{
		Element spriteElement = doc.createElement(SPRITE_TYPE);
		spriteElement.setAttribute(FILE_NAME_ATTR,spriteDescriptor.getId());
		spriteElement.setAttribute(TYPE_ATTR,spriteDescriptor.getType());
		spriteElement.setAttribute(CATEGORY_ATTR,category);
		if (spriteDescriptor.getXoffset() != 0)
			spriteElement.setAttribute(XOFFSET_ATTR,spriteDescriptor.getXoffset() + "");
		if (spriteDescriptor.getYoffset() != 0)
			spriteElement.setAttribute(XOFFSET_ATTR,spriteDescriptor.getYoffset() + "");
		return spriteElement;
	}
}
